﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

using CompleteReader.Common;

using pdftron.PDF;
using pdftron.SDF;
using CompleteReader.Utilities;
using Windows.UI;

// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237

namespace CompleteReader
{
    /// <summary>
    /// A basic page that provides characteristics common to most applications.
    /// </summary>
    public sealed partial class DocumentsPage : Windows.UI.Xaml.Controls.Page
    {

        private NavigationHelper navigationHelper;
        private ObservableDictionary defaultViewModel = new ObservableDictionary();

        private StorageFile _SelectedStorageFile;
        private bool _IsSelectedStorageFileRecent;
        private bool _ShouldUpdateRecentList = false;
        private bool _IsReadOnly = false;
        private bool _IsEncrypted = false;
        private int _PasswordAttempts;

        private PDFDoc _CurrentPDFDoc;

        // This makes sure that a double click on the browse button doesn't cause a crash.
        private bool _FilePickerOpen = false;

        // This lets us pass a cancel flag as reference.
        private class FileOpeningCanceler
        {
            public bool Cancel = false;
        }
        private FileOpeningCanceler _CurrentFileOpeningCanceler;

        /// <summary>
        /// This can be changed to a strongly typed view model.
        /// </summary>
        public ObservableDictionary DefaultViewModel
        {
            get { return this.defaultViewModel; }
        }

        /// <summary>
        /// NavigationHelper is used on each page to aid in navigation and 
        /// process lifetime management
        /// </summary>
        public NavigationHelper NavigationHelper
        {
            get { return this.navigationHelper; }
        }


        public DocumentsPage()
        {
            this.InitializeComponent();
            this.navigationHelper = new NavigationHelper(this);


            this.navigationHelper.LoadState += navigationHelper_LoadState;
            this.navigationHelper.SaveState += navigationHelper_SaveState;
            DocumentsPageMainContent.Navigate(typeof(DocumentsPageDialogs.RecentFilePage), this);
            RegisterListeners();
            pageTitle.Text = Settings.Settings.DisplayName.ToUpper();            
        }


        /// <summary>
        /// Populates the page with content passed during navigation. Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="sender">
        /// The source of the event; typically <see cref="NavigationHelper"/>
        /// </param>
        /// <param name="e">Event data that provides both the navigation parameter passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
        /// a dictionary of state preserved by this page during an earlier
        /// session. The state will be null the first time a page is visited.</param>
        private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
        {
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
        /// <param name="e">Event data that provides an empty dictionary to be populated with
        /// serializable state.</param>
        private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
        {
        }

        #region NavigationHelper registration

        /// The methods provided in this section are simply used to allow
        /// NavigationHelper to respond to the page's navigation methods.
        /// 
        /// Page specific logic should be placed in event handlers for the  
        /// <see cref="GridCS.Common.NavigationHelper.LoadState"/>
        /// and <see cref="GridCS.Common.NavigationHelper.SaveState"/>.
        /// The navigation parameter is available in the LoadState method 
        /// in addition to page state preserved during an earlier session.

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            navigationHelper.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            navigationHelper.OnNavigatedFrom(e);
            UpdateListOnNavigation();
        }

        private async void UpdateListOnNavigation()
        {
            if (_ShouldUpdateRecentList && _SelectedStorageFile != null)
            {
                Data.RecentItemsData recents = await Data.RecentItemsData.GetItemSourceAsync(this);
                if (_IsSelectedStorageFileRecent)
                {
                    //await recents.UpdateWithRecentFileAsync(_SelectedStorageFile);
                    recents.UpdateWithRecentFile(_SelectedStorageFile, _IsEncrypted);
                }
                else
                {
                    //await recents.UpdateWithNewFileAsync(_SelectedStorageFile);
                    recents.UpdateWithNewFile(_SelectedStorageFile, _IsEncrypted);
                }
                if (!_IsEncrypted)
                {
                    pdftron.Common.RecentlyUsedCache.AccessDocument(_SelectedStorageFile.Path);
                }
            }
        }

        #endregion

        private void pageRoot_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (e.NewSize.Width <= 600)
            {
                VisualStateManager.GoToState(this, "NarrowLayout", true);
            }
            else
            {
                VisualStateManager.GoToState(this, "DefaultLayout", true);
            }
        }


        /// <summary>
        /// When we get a file from the recent list
        /// </summary>
        /// <param name="file">The file to open</param>
        void recentPage_RecentItemOpened(StorageFile file)
        {
            CancelCurrentDocLoad();
            _IsReadOnly = false;
            _IsSelectedStorageFileRecent = true;
            _ShouldUpdateRecentList = true;
            if (file != null)
            {
                Open(file);
            }
        }


        private async void GettingStartedButton_Click(object sender, RoutedEventArgs e)
        {
            CancelCurrentDocLoad();
            StorageFile file = null;
            try
            {
                StorageFolder folder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("Resources");
                file = await folder.GetFileAsync("GettingStarted.pdf");
            }
            catch (Exception) { }

            if (file != null)
            {
                _IsReadOnly = true;
                _ShouldUpdateRecentList = false;
                Open(file, true);
            }
            else
            {
                Windows.ApplicationModel.Resources.ResourceLoader loader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
                MessageDialog md = new MessageDialog(loader.GetString("DocumentsPage_FileOpeningError_UsedByAnother_Info"), loader.GetString("DocumentsPage_FileOpeningError_Title"));
                await Utilities.MessageDialogHelper.ShowMessageDialogAsync(md);
            }
        }


        private void BrowseButton_Click(object sender, RoutedEventArgs e)
        {
            AnalyticsHandler.CURRENT.SendEvent("[File Browser] File Browser opened");

            _IsReadOnly = false;
            Browse();
        }

        private async void Browse()
        {
            if (_FilePickerOpen)
            {
                return;
            }
            CancelCurrentDocLoad();
            FileOpenPicker fileOpenPicker = new FileOpenPicker();
            fileOpenPicker.ViewMode = PickerViewMode.List;
            StorageFile file = null;
            fileOpenPicker.FileTypeFilter.Add(".pdf");
            try
            {
                _FilePickerOpen = true;
                // apparently, this sometimes throws a System.Exception "Element not found" for no apparent reason. We want to catch that.
                file = await fileOpenPicker.PickSingleFileAsync();
            }
            catch (Exception)
            {
            }
            finally
            {
                _FilePickerOpen = false;
            }

            if (file != null)
            {
                _IsSelectedStorageFileRecent = false;
                _ShouldUpdateRecentList = DoesFileBelongInRecentList(file);
                Open(file);
            }
        }


        public void OpenFileFromLaunch(StorageFile file)
        {
            _IsSelectedStorageFileRecent = false;
            _ShouldUpdateRecentList = DoesFileBelongInRecentList(file);
            Open(file);
        }

        private bool DoesFileBelongInRecentList(StorageFile file)
        {
            if (string.IsNullOrWhiteSpace(file.Path))
            {
                return false;
            }
            else
            {
                System.Text.RegularExpressions.Regex fileFromOtherFileRegex = new System.Text.RegularExpressions.Regex(@"\\packages\\.*\\localstate",
                    System.Text.RegularExpressions.RegexOptions.IgnoreCase);
                System.Text.RegularExpressions.Match match = fileFromOtherFileRegex.Match(file.Path);
                if (match.Success)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Opens a file through the file picker
        /// </summary>
        private async void Open(StorageFile file, bool isReadOnly = false)
        {
            _SelectedStorageFile = file;
            _IsEncrypted = false;
            if (file != null)
            {
                bool success = true;
                bool fileOpenedSuccessfully = false;
                bool pdfDocCreatedSuccessfully = false;

                PDFDoc doc = null;

                try
                {
                    FileOpeningCanceler canceler = new FileOpeningCanceler();
                    _CurrentFileOpeningCanceler = canceler;
                    StartProgressBarAfterWait(canceler);

                    _IsReadOnly = isReadOnly;

                    if (canceler.Cancel)
                    {
                        return;
                    }
                    try
                    {
                        doc = await GetPDFDocAsync(file);
                        pdfDocCreatedSuccessfully = true;
                    }
                    catch (Exception e)
                    {
                        success = false;
                        System.Diagnostics.Debug.WriteLine("Failed to create PDFDoc:\n" + e.ToString());
                        // check the type of error here?
                    }

                    if (pdfDocCreatedSuccessfully)
                    {
                        _CurrentPDFDoc = doc;
                        if (canceler.Cancel)
                        {
                            return;
                        }
                        success = true;
                        _SelectedStorageFile = file;
                    }

                }
                catch (Exception)
                {
                    success = false;
                }

                bool corrupt = false;
                if (pdfDocCreatedSuccessfully)
                {
                    try
                    {
                        if (!doc.InitStdSecurityHandler(""))
                        {
                            DocumentOpeningProgressbar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                            AnalyticsHandler.CURRENT.SendEvent("[Viewer] Document Opened Encrypted");

                            _CurrentFileOpeningCanceler.Cancel = true;

                            HandlePassword();
                            return;
                        }
                    }
                    catch (System.Exception)
                    {
                        corrupt = true;
                    }

                }
                else if (!fileOpenedSuccessfully)
                {
                    CancelCurrentDocLoad();

                    Windows.ApplicationModel.Resources.ResourceLoader loader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
                    MessageDialog md = new MessageDialog(loader.GetString("DocumentsPage_FileOpeningError_UsedByAnother_Info"), loader.GetString("DocumentsPage_FileOpeningError_Title"));
                    await CompleteReader.Utilities.MessageDialogHelper.ShowMessageDialogAsync(md);
                    return;
                }

                if (corrupt)
                {
                    Windows.ApplicationModel.Resources.ResourceLoader loader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
                    MessageDialog md = new MessageDialog(string.Format(loader.GetString("DocumentsPage_FileOpeningError_CorruptFile_Info"), Settings.Settings.DisplayName), loader.GetString("DocumentsPage_FileOpeningError_Title"));
                    await CompleteReader.Utilities.MessageDialogHelper.ShowMessageDialogAsync(md);
                    return;
                }

                if (success && !corrupt)
                {
                    if (IsXFA())
                    {
                        OpenXFA();
                    }
                    else if (IsPackage())
                    {
                        OpenPackage();
                    }
                    else
                    {
                        this.Frame.Navigate(typeof(ViewerPage), new Data.NavigationParameter(_CurrentPDFDoc, _SelectedStorageFile, null, _IsReadOnly));
                    }
                }

            }
        }

        private IAsyncOperation<PDFDoc> GetPDFDocAsync(StorageFile file)
        {
            Task<PDFDoc> t = new Task<PDFDoc>(() =>
            {
                return GetPDFDoc(file);
            });
            t.Start();
            return t.AsAsyncOperation<PDFDoc>();
        }


        private PDFDoc GetPDFDoc(StorageFile file)
        {
            return new PDFDoc(file);
        }

        private async void StartProgressBarAfterWait(FileOpeningCanceler canceler)
        {
            System.Diagnostics.Debug.WriteLine("Awaiting before progress bar");
            await Task.Delay(1000);
            System.Diagnostics.Debug.WriteLine("waiting before progress bar done");
            if (!canceler.Cancel)
            {
                DocumentOpeningProgressbar.Visibility = Windows.UI.Xaml.Visibility.Visible;
            }
        }

        private void CancelCurrentDocLoad()
        {
            if (_CurrentFileOpeningCanceler != null)
            {
                _CurrentFileOpeningCanceler.Cancel = true;
            }
            DocumentOpeningProgressbar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
        }

        private void RegisterListeners()
        {
            DocumentsPageDialogs.RecentFilePage recentPage = DocumentsPageMainContent.Content as DocumentsPageDialogs.RecentFilePage;
            if(recentPage != null)
            {
                recentPage.RecentItemOpened += recentPage_RecentItemOpened;
            }
        }

        #region Password

        private void HandlePassword()
        {
            _IsEncrypted = true;
            _PasswordAttempts = 3;
            DocumentsPageDialogs.RecentFilePage recent = DocumentsPageMainContent.Content as DocumentsPageDialogs.RecentFilePage;
            recent.DetachAppbar();

            MainViewerPageControl.IsEnabled = false;
            this.PasswordGrid.Visibility = Windows.UI.Xaml.Visibility.Visible;
            this.PasswordDialog.Visibility = Windows.UI.Xaml.Visibility.Visible;
            this.PasswordDialog.SetFocus();
            this.PasswordDialog.Reset();
        }

        void PasswordDialog_PasswordEntered(string password)
        {
            if (password == null)
            {
                MainViewerPageControl.IsEnabled = true;
                this.PasswordDialog.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                DocumentsPageDialogs.RecentFilePage recent = DocumentsPageMainContent.Content as DocumentsPageDialogs.RecentFilePage;
                recent.AttachAppbar();
                _CurrentPDFDoc.Dispose();
            }
            else
            { 
                if (_CurrentPDFDoc.InitStdSecurityHandler(password))
                {
                    if (IsXFA()) OpenXFA();
                }
                else
                {
                    this.PasswordDialog.NotifyIncorrectPassword();
                    _PasswordAttempts--;
                    if (_PasswordAttempts == 0)
                    {
                        PasswordDialog_PasswordEntered(null);
                    }
                    else
                    {
                        this.PasswordDialog.SetFocus();
                    }
                }
            }
        }

        #endregion Password

        #region XFA Handling

        private bool IsXFA()
        {
            Obj needsRenderingObj = _CurrentPDFDoc.GetRoot().FindObj("NeedsRendering");
            return (needsRenderingObj != null && needsRenderingObj.IsBool() && needsRenderingObj.GetBool());
        }

        private async void OpenXFA()
        {
            //Obj needsRenderingObj = _CurrentPDFDoc.GetRoot().FindObj("NeedsRendering");
            //if (needsRenderingObj != null && needsRenderingObj.IsBool() && needsRenderingObj.GetBool())
            //{
            DocumentOpeningProgressbar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            AnalyticsHandler.CURRENT.SendEvent("[Viewer] Document Opened XFA");
            _CurrentFileOpeningCanceler.Cancel = true;
            Windows.ApplicationModel.Resources.ResourceLoader loader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
            MessageDialog md = new MessageDialog(loader.GetString("DocumentsPage_FileOpeningError_XFA_Info"), loader.GetString("DocumentsPage_FileOpeningError_Title"));
            await Utilities.MessageDialogHelper.ShowMessageDialogAsync(md);
        }


        #endregion XFA Handling

        #region PDF Package

        private bool IsPackage()
        {
            return _CurrentPDFDoc.GetRoot().FindObj("Collection") != null;
        }

        private void OpenPackage()
        {
            Obj collectionObj = _CurrentPDFDoc.GetRoot().FindObj("Collection");
            if (collectionObj != null)
            {
                AnalyticsHandler.CURRENT.SendEvent("[Viewer] Document Opened Package");
                HandlePackage();
            }
            //else
            //{
            //    this.Frame.Navigate(typeof(ViewerPage), new Data.NavigationParameter(_CurrentPDFDoc, _SelectedStorageFile, _IsReadOnly));
            //}
        }


        private void HandlePackage()
        {
            _IsReadOnly = true;
            DocumentsPageDialogs.RecentFilePage recent = DocumentsPageMainContent.Content as DocumentsPageDialogs.RecentFilePage;
            recent.DetachAppbar();

            DocumentOpeningProgressbar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            _CurrentFileOpeningCanceler.Cancel = true;

            MainViewerPageControl.IsEnabled = false;
            this.PDFPackageGrid.Visibility = Windows.UI.Xaml.Visibility.Visible;
            this.PackageDialog.Visibility = Windows.UI.Xaml.Visibility.Visible;
            this.PackageDialog.PDFDoc = _CurrentPDFDoc;
        }

        private void PackageDialog_FileSelcted(PDFDoc doc, bool err, string errorMessage)
        {
            if (doc != null)
            {
                _SelectedStorageFile = null;
                this.Frame.Navigate(typeof(ViewerPage), new Data.NavigationParameter(doc, null, null, _IsReadOnly));
                _CurrentPDFDoc.Dispose();
            }
            else if (err)
            {
                Windows.ApplicationModel.Resources.ResourceLoader loader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
                MessageDialog msg = new MessageDialog(errorMessage, loader.GetString("PackageDialog_FileExtractionError_Title"));
            }
            else
            {
                MainViewerPageControl.IsEnabled = true;
                this.PackageDialog.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                DocumentsPageDialogs.RecentFilePage recent = DocumentsPageMainContent.Content as DocumentsPageDialogs.RecentFilePage;
                recent.AttachAppbar();
                _CurrentPDFDoc.Dispose();
            }
        }

        #endregion PDFPackage

        //private void ResetSettingsButton_Click(object sender, RoutedEventArgs e)
        //{
        //    Settings.Settings.DebugReset();
        //}        
    }
}
